package com.alcloud.springplus.base;

import com.alcloud.springplus.exception.ApplicationException;
import com.alcloud.springplus.util.SearchFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @className: GenericServiceImpl
 * @description: 通用service 实现类
 * @author: AllG
 * @createDate: 2018/12/4 13:53
 * @updateRemark: 更新说明
 */
@org.springframework.transaction.annotation.Transactional(readOnly = true)
public abstract class GenericServiceImpl<T, ID> implements GenericService<T, ID> {

    private final static Logger logger = LoggerFactory.getLogger(GenericServiceImpl.class);

    protected GenericDao<T, ID> genericDao;

    @Autowired
    public GenericServiceImpl(GenericDao<T, ID> genericDao) {
        this.genericDao = genericDao;
    }

    @org.springframework.transaction.annotation.Transactional(rollbackFor = ApplicationException.class)
    @Override
    public <S extends T> S save(S entity) {
        S result = null;
        try {
            result = genericDao.save(entity);
        } catch (DuplicateKeyException e) {
            logger.error(ResponseCode.INSERT_DUPLICATE.getMessage(), e);
            throw new ApplicationException(ResponseCode.INSERT_DUPLICATE);
        } catch (Exception e) {
            logger.error(ResponseCode.INSERT_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.INSERT_EXCEPTION);
        }
        return result;
    }

    @org.springframework.transaction.annotation.Transactional(rollbackFor = ApplicationException.class)
    @Override
    public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
        Iterable<S> result = null;
        try {
            result = genericDao.saveAll(entities);
        } catch (Exception e) {
            logger.error(ResponseCode.INSERT_BATCH_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.INSERT_BATCH_EXCEPTION);
        }
        return result;
    }


    @Override
    public Optional<T> findById(ID id) {
        Optional<T> result = null;
        try {
            result = genericDao.findById(id);
        } catch (Exception e) {
            logger.error(ResponseCode.SELECT_ONE_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.SELECT_ONE_EXCEPTION);
        }
        return result;
    }

    @Override
    public boolean existsById(ID id) {
        boolean result = false;
        try {
            result = genericDao.existsById(id);
        } catch (Exception e) {
            logger.error(ResponseCode.SELECT_ONE_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.SELECT_ONE_EXCEPTION);
        }
        return result;
    }

    @Override
    public List<T> findAll(Optional<T> entity) {
        List<T> result ;
        try {
            if (entity.isPresent()) {
                result = genericDao.findAll(Example.of(entity.get()));
            } else {
                result = genericDao.findAll();
            }
        } catch (Exception e) {
            logger.error(ResponseCode.SELECT_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.SELECT_EXCEPTION);
        }
        return result;
    }

    @Override
    public Iterable<T> findAllById(Iterable<ID> ids) {
        Iterable<T> result;
        try {
            result = genericDao.findAllById(ids);
        } catch (Exception e) {
            logger.error(ResponseCode.SELECT_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.SELECT_EXCEPTION);
        }
        return result;
    }

    @Override
    public long count(Optional<T> entity) {
        long result ;
        try {
            if (entity.isPresent()) {
                result = genericDao.count(Example.of(entity.get()));
            } else {
                result = genericDao.count();
            }
        } catch (Exception e) {
            logger.error(ResponseCode.SELECT_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.SELECT_EXCEPTION);
        }
        return result;
    }

    @org.springframework.transaction.annotation.Transactional(rollbackFor = ApplicationException.class)
    @Override
    public void deleteById(ID id) {
        try {
            genericDao.deleteById(id);
        } catch (Exception e) {
            logger.error(ResponseCode.DELETE_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.DELETE_EXCEPTION);
        }
    }

    @Transactional(rollbackFor = ApplicationException.class)
    @Override
    public void delete(T entity) {
        try {
            genericDao.delete(entity);
        } catch (Exception e) {
            logger.error(ResponseCode.DELETE_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.DELETE_EXCEPTION);
        }
    }

    @Override
    public void deleteAll(Iterable<? extends T> entities) {
        try {
            genericDao.deleteAll(entities);
        } catch (Exception e) {
            logger.error(ResponseCode.DELETE_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.DELETE_EXCEPTION);
        }
    }

    @Override
    public <S extends T> Page<S> findAll(Map<String, String[]> params, Pageable pageable) {
        Page<S> result;
        try {
            Collection<SearchFilter> filters = SearchFilter.parse(params).values();
            final Specification<S> fsp = SearchFilter.spec(filters, (Class<S>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
            result = genericDao.findAll(fsp, pageable);
        } catch (Exception e) {
            logger.error(ResponseCode.SELECT_PAGINATION_EXCEPTION.getMessage(), e);
            throw new ApplicationException(ResponseCode.SELECT_PAGINATION_EXCEPTION);
        }
        return result;
    }

}